package com.want.method.handler;

import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ArrayUtil;
import com.want.client.Client;
import com.want.method.MethodInfo;
import com.want.service.ServiceInfo;
import com.want.util.UriUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpMethod;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.ClientRequest;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.util.DefaultUriBuilderFactory;
import org.springframework.web.util.UriBuilderFactory;
import reactor.core.publisher.Flux;

import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 *
 *
 * @author WangZhiJian
 * @since 2021/12/5 0005
 */
@Slf4j
public class WantReactorInvocationHandler implements InvocationHandler {
    /** 接口的类实例*/
    private Class<?> type;
    /** 请求的客户端*/
    private Client client;
    /** uri的构建工厂*/
    private UriBuilderFactory uriBuilderFactory;
    /** 服务信息*/
    private ServiceInfo serviceInfo;
    /** 缓存的方法信息*/
    private Map<String, MethodInfo> cachedMethodInfo;
    /** 锁Map,如果是懒加载方法信息的话使用*/
    private Map<String, Lock> lockMap;
    /** 是否懒加载方法信息*/
    private Boolean lazyInitMethod;
    public WantReactorInvocationHandler(Client client, UriBuilderFactory uriBuilderFactory
            , Class<?> type,Boolean lazyInitMethod,ServiceInfo serviceInfo) {
        this.type = type;
        this.client = client;
        this.serviceInfo = serviceInfo;
        this.uriBuilderFactory = uriBuilderFactory;
        this.lazyInitMethod = lazyInitMethod;
        init();
    }
    public WantReactorInvocationHandler(Client client, Class<?> type,ServiceInfo serviceInfo) {
        this.type = type;
        this.client = client;
        this.serviceInfo = serviceInfo;
        this.uriBuilderFactory = new DefaultUriBuilderFactory();
        this.lazyInitMethod = false;
        init();
    }
    public WantReactorInvocationHandler(Class<?> type,ServiceInfo serviceInfo) {
        this.type = type;
        this.client = new Client.DefaultClient();
        this.serviceInfo = serviceInfo;
        this.uriBuilderFactory = new DefaultUriBuilderFactory();
        this.lazyInitMethod = false;
        init();
    }

    private void init(){
        // 初始化缓存Map\锁Map
        cachedMethodInfo = new ConcurrentHashMap<>();
        lockMap = new ConcurrentHashMap<>();
//        // 获取服务信息
//        serviceInfo = extractServerInfo(type);
        // 如果懒加载的话直接返回
        if( Optional.ofNullable(lazyInitMethod).orElse(false) )
            return;
        // 默认初始化 不支持继承,获取所有的方法
        Method[] methods = type.getMethods();
        if(ArrayUtil.isEmpty(methods))
            return;
        // 遍历方法获取方法信息放入缓存
        for(Method m : methods){
            String key = m.toString();
            MethodInfo methodInfo = extractMethodInfo(m);
            cachedMethodInfo.put(key, methodInfo);
        }
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        String key = method.toString();
        // 缓存中的方法信息
        MethodInfo targetMethodInfo = cachedMethodInfo.get(key);
        // 无缓存的方法信息则初始化方法信息,使用双重检查初始化方法信息
        if(targetMethodInfo == null)
            targetMethodInfo = doubleCheckInitMethodInfo(key,method);
        // 将实际参数和方法信息中的参数类型信息结合生产实际方法参数
        MethodParam methodParam = extractRequestParamAndBody(args, targetMethodInfo);
        // 根据服务信息和方法信息及实际方法参数 进行 远程调用
        return invokeRest(serviceInfo,targetMethodInfo,methodParam);
    }


    private MethodInfo doubleCheckInitMethodInfo(String key,Method method){
        MethodInfo targetMethodInfo;
        lockMap.putIfAbsent(key,new ReentrantLock());
        Lock lock = lockMap.get(key);
        lock.lock();
        try{
            targetMethodInfo = cachedMethodInfo.get(key);
            if(targetMethodInfo == null){
                targetMethodInfo = extractMethodInfo(method);
                cachedMethodInfo.put(key, targetMethodInfo);
            }
        }finally {
            lock.unlock();
        }
        return targetMethodInfo;
    }

    private Object invokeRest(ServiceInfo serviceInfo,MethodInfo methodInfo,MethodParam methodParam) throws URISyntaxException {
        StringBuilder targetUriStr = new StringBuilder();
        // 将serviceInfo的uri信息和methodInfo的uri组合在一起
        String uri = methodInfo.getUrl();
        String baseUri = null;
        if(StringUtils.hasText(serviceInfo.getUri())){
           baseUri = serviceInfo.getUri();
        }else if(StringUtils.hasText(serviceInfo.getServiceName())){
            baseUri = serviceInfo.getServiceName();
        }else {
            throw new RuntimeException("必须要有uri或者serviceName");
        }
        if(baseUri.startsWith("http://"))
            uri = UriUtil.contactPath(baseUri,serviceInfo.getNextPath(),uri);
        else
            uri = UriUtil.contactPath("http://",baseUri,serviceInfo.getNextPath(),uri);

        // 如果路径中携带参数,则使用uriBuilderFactory进行替换
        if(MapUtil.isNotEmpty(methodParam.getParams())){
            targetUriStr.append(uriBuilderFactory.expand(uri,methodParam.getParams()));
        }else {
            targetUriStr.append(uri);
        }
        // 如果存在路径后跟随查询参数的情况,则追加在uri后面
        if(MapUtil.isNotEmpty(methodParam.getAttributes())){
            String queryStr = methodParam.getAttributes().entrySet()
                    .stream().filter(entry -> entry.getValue() != null && entry.getValue().size() > 0)
                    .map(entry -> entry.getKey() + "=" + String.join(",", entry.getValue()))
                    .collect(Collectors.joining("&"));
            if(queryStr.length() > 0)
                targetUriStr.append("?").append(queryStr);
        }
        // 构建最终的uri
        URI targetUri = new URI(targetUriStr.toString());
        // 创建请求
        ClientRequest.Builder clientBuilder = ClientRequest.create(methodInfo.getMethod(),targetUri);
        // 如果有body的话讲body放入请求中
        Optional.ofNullable(methodParam.getBody())
                .map(BodyInserters::fromValue)
                .ifPresent(clientBuilder::body);
        // 通过client执行该请求
        // TODO 添加filter
        try{
            WebClient.ResponseSpec responseSpec = client.exec(clientBuilder.build());
            // TODO 添加errorDecode
            // 返回响应的实体
            if(methodInfo.isReturnFlux())
                return responseSpec
                        .bodyToFlux(ParameterizedTypeReference.forType(methodInfo.getReturnElementType()));
            return responseSpec
                    .bodyToMono(ParameterizedTypeReference.forType(methodInfo.getReturnElementType()));
        }catch (Throwable throwable){
            log.error("请求出现异常！",throwable);
            throw new RuntimeException(throwable);
        }

    }

    /**
     * 根据方法定义和调用参数得到调用的相关信息
     *
     * @param method
     * @return
     */
    private MethodInfo extractMethodInfo(Method method) {
        MethodInfo methodInfo = new MethodInfo();
        // 获取url和请求类型
        extractUrlAndMethod(method, methodInfo);
        //获取参数类型和参数名信息
        extractParam(method, methodInfo);
        // 提取返回对象的信息
        extractReturnInfo(method, methodInfo);
        return methodInfo;
    }

    private void extractParam(Method method, MethodInfo methodInfo) {
        // 得到调用的参数和body
        Parameter[] parameters = method.getParameters();
        if(parameters == null || parameters.length == 0){
            methodInfo.setParamTypeEnum(Collections.emptyList());
            return;
        }
        // 参数和值对应的map
        List<MethodInfo.ParamInfo> paramInfos = new ArrayList<>();
        for (int i = 0; i < parameters.length; i++) {
            // 是否带 @PathVariable注解
            PathVariable annoPath = parameters[i].getAnnotation(PathVariable.class);
            if (annoPath != null) {
                paramInfos.add(MethodInfo.ParamInfo.builder().paramTypeEnum(MethodInfo.ParamTypeEnum.PARAM).key(annoPath.value()).build());
                continue;
            }
            // 是否带了 RequestBody
            RequestBody annoBody = parameters[i].getAnnotation(RequestBody.class);
            if (annoBody != null) {
                paramInfos.add(MethodInfo.ParamInfo.builder().paramTypeEnum(MethodInfo.ParamTypeEnum.BODY).build());
                continue;
            }
            RequestParam annoParam = parameters[i].getAnnotation(RequestParam.class);
            if(annoParam != null){
                paramInfos.add(MethodInfo.ParamInfo.builder().paramTypeEnum(MethodInfo.ParamTypeEnum.ATTRIBUTES).key(annoParam.value()).build());
                continue;
            }
            String defaultKey = parameters[i].getName();
            paramInfos.add(MethodInfo.ParamInfo.builder().paramTypeEnum(MethodInfo.ParamTypeEnum.ATTRIBUTES).key(defaultKey).build());
        }
        methodInfo.setParamTypeEnum(paramInfos);
    }

    /**
     * 提取返回对象信息
     *
     * @param method
     * @param methodInfo
     */
    private void extractReturnInfo(Method method, MethodInfo methodInfo) {
        // 返回flux还是mono
        // isAssignableFrom 判断类型是否是某个类的子类
        // instanceof 判断实例是否是某个类的子类
        boolean isFlux = method.getReturnType().isAssignableFrom(Flux.class);
        methodInfo.setReturnFlux(isFlux);

        // 得到返回对象的实际类型
        Type elementType = extractElementType(method.getGenericReturnType());
        methodInfo.setReturnElementType(elementType);
    }

    /**
     * 得到反省类型的实际类型
     *
     * @param genericReturnType
     * @return
     */
    private Type extractElementType(Type genericReturnType) {
        Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
        return actualTypeArguments[0];
    }

    /**
     * 得到请求的param和body
     *
     * @param args
     * @param methodInfo
     */
    private MethodParam extractRequestParamAndBody(Object[] args, MethodInfo methodInfo) {
        MethodParam result = new MethodParam();
        // 得到调用的参数和body
        List<MethodInfo.ParamInfo> paramInfos = methodInfo.getParamTypeEnum();
        // 参数和值对应的map
        Map<String, Object> params = new LinkedHashMap<>();
        MultiValueMap<String, String> attributes = new LinkedMultiValueMap<>();
        for (int i = 0; i < paramInfos.size(); i++) {
            MethodInfo.ParamInfo paramInfo = paramInfos.get(i);
            // 根据参数类型进行赋值
            if(paramInfo.getParamTypeEnum() == MethodInfo.ParamTypeEnum.PARAM){
                params.put(paramInfo.getKey(), args[i]);
                continue;
            }else if(paramInfo.getParamTypeEnum() == MethodInfo.ParamTypeEnum.BODY){
                result.setBody(args[i]);
            }else if(paramInfo.getParamTypeEnum() == MethodInfo.ParamTypeEnum.ATTRIBUTES) {
                attributes.add(paramInfo.getKey(), args[i].toString());
            }
        }
        result.setParams(params);
        result.setAttributes(attributes);
        return result;
    }

    /**
     * 得到请求的url和方法
     *
     * @param method
     * @param methodInfo
     */
    private void extractUrlAndMethod(Method method, MethodInfo methodInfo) {
        // 得到请求URL和请求方法
        Annotation[] annotations = method.getAnnotations();
        for (Annotation annotation : annotations) {
            // GET
            if (annotation instanceof GetMapping) {
                GetMapping a = (GetMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.GET);
            }
            // POST
            else if (annotation instanceof PostMapping) {
                PostMapping a = (PostMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.POST);
            }
            // DELETE
            else if (annotation instanceof DeleteMapping) {
                DeleteMapping a = (DeleteMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.DELETE);
            }
            // PUT
            else if (annotation instanceof PutMapping) {
                PutMapping a = (PutMapping) annotation;
                methodInfo.setUrl(a.value()[0]);
                methodInfo.setMethod(HttpMethod.PUT);
            }
        }
    }
}
